#include "definitions.h"

#define USE_SERCOM0_USART      0
#define USE_SERCOM1_USART      0
#define USE_SERCOM2_USART      0
#define USE_SERCOM3_USART      0
#define USE_SERCOM4_USART      1
#define USE_SERCOM5_USART      0
#define USE_SERCOM6_USART      0

#if (USE_SERCOM4_USART > 0)
/* SERCOM4 USART baud value for 115200 Hz baud rate */
#define SERCOM4_USART_INT_BAUD_VALUE            (63019U)
#endif

/**
 * \brief Retrieve ordinal number of the given sercom hardware instance
 */
static uint8_t sercom_get_hardware_index (const void *const hw)
{
    return ((uint32_t)hw - (uint32_t)SERCOM0_REGS) >> 10;
}

// *****************************************************************************
// *****************************************************************************
// Section: SERCOM4 USART Interface Routines
// *****************************************************************************
// *****************************************************************************
static void SERCOM_USART_ClockInit ( void )
{
#if (USE_SERCOM3_USART > 0)
#endif
#if (USE_SERCOM4_USART > 0)
    /* Selection of the Generator and write Lock for SERCOM4_CORE */
    GCLK_REGS->GCLK_CLKCTRL = GCLK_CLKCTRL_ID (GCLK_CLKCTRL_ID_SERCOM4_CORE_Val) |
                              GCLK_CLKCTRL_GEN (GCLK_CLKCTRL_GEN_GCLK0_Val)  |
                              GCLK_CLKCTRL_CLKEN_Msk;

    /* Configure the APBC Bridge Clocks */
    PM_REGS->PM_APBCMASK |= PM_APBCMASK_SERCOM (1 << 4);
#endif
}

static void SERCOM_USART_PortInit ( void )
{
#if (USE_SERCOM3_USART > 0)
#endif
#if (USE_SERCOM4_USART > 0)
    /************************** GROUP 1 Initialization *************************/
    // PIN_PB08D_SERCOM4_PAD0, PIN_PB09D_SERCOM4_PAD1
    PORT_REGS->GROUP[1].PORT_PINCFG[PIN_PB08D_SERCOM4_PAD0 & 0x1F] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[1].PORT_PINCFG[PIN_PB09D_SERCOM4_PAD1 & 0x1F] = PORT_PINCFG_PMUXEN (1);

    PORT_REGS->GROUP[1].PORT_PMUX[4] = PORT_PMUX_PMUXO_D | PORT_PMUX_PMUXE_D;
#endif
}

static void SERCOM_USART_NvicInit ( void )
{
#if (USE_SERCOM3_USART > 0)
    NVIC_SetPriority (SERCOM3_IRQn, 3);
    NVIC_EnableIRQ (SERCOM3_IRQn);
#endif
#if (USE_SERCOM4_USART > 0)
    NVIC_SetPriority (SERCOM4_IRQn, 3);
    NVIC_EnableIRQ (SERCOM4_IRQn);
#endif
}

void static SERCOM_USART_ErrorClear ( sercom_registers_t *regs )
{
    uint8_t  u8dummyData = 0;

    /* Clear error flag */
    regs->USART_INT.SERCOM_INTFLAG = SERCOM_USART_INT_INTFLAG_ERROR_Msk;

    /* Clear all errors */
    regs->USART_INT.SERCOM_STATUS = SERCOM_USART_INT_STATUS_PERR_Msk | SERCOM_USART_INT_STATUS_FERR_Msk | SERCOM_USART_INT_STATUS_BUFOVF_Msk;

    /* Flush existing error bytes from the RX FIFO */
    while ((regs->USART_INT.SERCOM_INTFLAG & SERCOM_USART_INT_INTFLAG_RXC_Msk) == SERCOM_USART_INT_INTFLAG_RXC_Msk) {
        u8dummyData = regs->USART_INT.SERCOM_DATA;
    }

    /* Ignore the warning */
    (void)u8dummyData;
}

void SERCOM_USART_Initialize ( sercom_registers_t *regs )
{
    /*
    * Configures USART Clock Mode
    * Configures TXPO and RXPO
    * Configures Data Order
    * Configures Standby Mode
    * Configures Sampling rate
    * Configures IBON
    */
    regs->USART_INT.SERCOM_CTRLA = SERCOM_USART_INT_CTRLA_MODE_USART_INT_CLK |
                                   SERCOM_USART_INT_CTRLA_RXPO (0x1) | SERCOM_USART_INT_CTRLA_TXPO (0x0) |
                                   SERCOM_USART_INT_CTRLA_DORD_Msk | SERCOM_USART_INT_CTRLA_IBON_Msk |
                                   SERCOM_USART_INT_CTRLA_FORM (0x0) | SERCOM_USART_INT_CTRLA_SAMPR (0) ;

    /* Configure Baud Rate */
    regs->USART_INT.SERCOM_BAUD = SERCOM_USART_INT_BAUD_BAUD (SERCOM4_USART_INT_BAUD_VALUE);

    /*
     * Configures RXEN
     * Configures TXEN
     * Configures CHSIZE
     * Configures Parity
     * Configures Stop bits
     */
    regs->USART_INT.SERCOM_CTRLB = SERCOM_USART_INT_CTRLB_CHSIZE_8_BIT | SERCOM_USART_INT_CTRLB_SBMODE_1_BIT |
                                   SERCOM_USART_INT_CTRLB_RXEN_Msk | SERCOM_USART_INT_CTRLB_TXEN_Msk;

    /* Wait for sync */
    while (regs->USART_INT.SERCOM_SYNCBUSY);

    /* Enable the UART after the configurations */
    regs->USART_INT.SERCOM_CTRLA |= SERCOM_USART_INT_CTRLA_ENABLE_Msk;

    /* Wait for sync */
    while (regs->USART_INT.SERCOM_SYNCBUSY);

    /* Enable error interrupt */
    regs->USART_INT.SERCOM_INTENSET = SERCOM_USART_INT_INTENSET_ERROR_Msk;

    /* Enable Receive Complete interrupt */
    regs->USART_INT.SERCOM_INTENSET = SERCOM_USART_INT_INTENSET_RXC_Msk;
}

uint32_t SERCOM_USART_FrequencyGet ( sercom_registers_t *regs )
{
    uint8_t index = sercom_get_hardware_index (regs);
    uint32_t clock = 0;

    switch (index) {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
            clock = 48000000UL;
            break;
        default:
            break;
    }

    return clock;
}

bool SERCOM_USART_SerialSetup ( sercom_registers_t *regs, USART_SERIAL_SETUP *serialSetup, uint32_t clkFrequency )
{
    bool setupStatus       = false;
    uint32_t baudValue     = 0;
    uint32_t sampleRate    = 0;

    if ((serialSetup != NULL) & (serialSetup->baudRate != 0)) {
        if (clkFrequency == 0) {
            clkFrequency = SERCOM_USART_FrequencyGet (regs);
        }

        if (clkFrequency >= (16 * serialSetup->baudRate)) {
            baudValue = 65536 - ((uint64_t)65536 * 16 * serialSetup->baudRate) / clkFrequency;
            sampleRate = 0;
        } else if (clkFrequency >= (8 * serialSetup->baudRate)) {
            baudValue = 65536 - ((uint64_t)65536 * 8 * serialSetup->baudRate) / clkFrequency;
            sampleRate = 2;
        } else if (clkFrequency >= (3 * serialSetup->baudRate)) {
            baudValue = 65536 - ((uint64_t)65536 * 3 * serialSetup->baudRate) / clkFrequency;
            sampleRate = 4;
        }

        if (baudValue != 0) {
            /* Disable the USART before configurations */
            regs->USART_INT.SERCOM_CTRLA &= ~SERCOM_USART_INT_CTRLA_ENABLE_Msk;

            /* Wait for sync */
            while (regs->USART_INT.SERCOM_SYNCBUSY);

            /* Configure Baud Rate */
            regs->USART_INT.SERCOM_BAUD = SERCOM_USART_INT_BAUD_BAUD (baudValue);

            /* Configure Parity Options */
            if (serialSetup->parity == USART_PARITY_NONE) {
                regs->USART_INT.SERCOM_CTRLA =  (regs->USART_INT.SERCOM_CTRLA & ~ (SERCOM_USART_INT_CTRLA_SAMPR_Msk |
                                                 SERCOM_USART_INT_CTRLA_FORM_Msk)) |
                                                SERCOM_USART_INT_CTRLA_FORM (0x0) |
                                                SERCOM_USART_INT_CTRLA_SAMPR (sampleRate);
                regs->USART_INT.SERCOM_CTRLB = (regs->USART_INT.SERCOM_CTRLB & ~ (SERCOM_USART_INT_CTRLB_CHSIZE_Msk |
                                                SERCOM_USART_INT_CTRLB_SBMODE_Pos)) |
                                               ((uint32_t) serialSetup->dataWidth |
                                                (uint32_t) serialSetup->stopBits);
            } else {
                regs->USART_INT.SERCOM_CTRLA =  (regs->USART_INT.SERCOM_CTRLA & ~ (SERCOM_USART_INT_CTRLA_SAMPR_Msk |
                                                 SERCOM_USART_INT_CTRLA_FORM_Msk)) |
                                                SERCOM_USART_INT_CTRLA_FORM (0x1) |
                                                SERCOM_USART_INT_CTRLA_SAMPR (sampleRate);
                regs->USART_INT.SERCOM_CTRLB = (regs->USART_INT.SERCOM_CTRLB & ~ (SERCOM_USART_INT_CTRLB_CHSIZE_Msk |
                                                SERCOM_USART_INT_CTRLB_SBMODE_Pos |
                                                SERCOM_USART_INT_CTRLB_PMODE_Msk)) |
                                               (uint32_t) serialSetup->dataWidth |
                                               (uint32_t) serialSetup->stopBits |
                                               (uint32_t) serialSetup->parity ;
            }

            /* Wait for sync */
            while (regs->USART_INT.SERCOM_SYNCBUSY);

            /* Enable the USART after the configurations */
            regs->USART_INT.SERCOM_CTRLA |= SERCOM_USART_INT_CTRLA_ENABLE_Msk;

            /* Wait for sync */
            while (regs->USART_INT.SERCOM_SYNCBUSY);

            setupStatus = true;
        }
    }

    return setupStatus;
}

USART_ERROR SERCOM_USART_ErrorGet ( sercom_registers_t *regs )
{
    USART_ERROR errorStatus = USART_ERROR_NONE;

    errorStatus = (USART_ERROR) (regs->USART_INT.SERCOM_STATUS & (SERCOM_USART_INT_STATUS_PERR_Msk |
                                 SERCOM_USART_INT_STATUS_FERR_Msk |
                                 SERCOM_USART_INT_STATUS_BUFOVF_Msk));

    if (errorStatus != USART_ERROR_NONE) {
        SERCOM_USART_ErrorClear (regs);
    }

    return errorStatus;
}


void SERCOM_USART_TransmitterEnable ( sercom_registers_t *regs )
{
    regs->USART_INT.SERCOM_CTRLB |= SERCOM_USART_INT_CTRLB_TXEN_Msk;

    /* Wait for sync */
    while (regs->USART_INT.SERCOM_SYNCBUSY);
}

void SERCOM_USART_TransmitterDisable ( sercom_registers_t *regs )
{
    regs->USART_INT.SERCOM_CTRLB &= ~SERCOM_USART_INT_CTRLB_TXEN_Msk;

    /* Wait for sync */
    while (regs->USART_INT.SERCOM_SYNCBUSY);
}

void SERCOM_USART_ReceiverEnable ( sercom_registers_t *regs )
{
    regs->USART_INT.SERCOM_CTRLB |= SERCOM_USART_INT_CTRLB_RXEN_Msk;

    /* Wait for sync */
    while (regs->USART_INT.SERCOM_SYNCBUSY);
}

void SERCOM_USART_ReceiverDisable ( sercom_registers_t *regs )
{
    regs->USART_INT.SERCOM_CTRLB &= ~SERCOM_USART_INT_CTRLB_RXEN_Msk;

    /* Wait for sync */
    while (regs->USART_INT.SERCOM_SYNCBUSY);
}

void static SERCOM_USART_ISR_ERR_Handler ( sercom_registers_t *regs )
{
    USART_ERROR errorStatus = USART_ERROR_NONE;

    errorStatus = (USART_ERROR) (regs->USART_INT.SERCOM_STATUS &
                                 (SERCOM_USART_INT_STATUS_PERR_Msk |
                                  SERCOM_USART_INT_STATUS_FERR_Msk |
                                  SERCOM_USART_INT_STATUS_BUFOVF_Msk));

    if (errorStatus != USART_ERROR_NONE) {
        /* Clear error and receive interrupt to abort on-going transfer */
        regs->USART_INT.SERCOM_INTENCLR = SERCOM_USART_INT_INTENCLR_ERROR_Msk | SERCOM_USART_INT_INTENCLR_RXC_Msk;

        /* In case of errors are not cleared by client using ErrorGet API */
        SERCOM_USART_ErrorClear (regs);
    }
}


#include <rtthread.h>

#if defined(RT_USING_DEVICE) && defined(RT_USING_SERIAL)

#include "rtdevice.h"

typedef struct _samd2x_uart_t {
    struct rt_serial_device *serial;
    sercom_registers_t *sercom_reg;
    IRQn_Type vector;
} SAMD2x_UART_T;

static struct rt_serial_device _serial4;

static SAMD2x_UART_T _uart4 = {
    &_serial4,
    SERCOM4_REGS,
    SERCOM4_IRQn,
};

static rt_err_t _uart_cfg (struct rt_serial_device *serial, struct serial_configure *cfg)
{
    SAMD2x_UART_T *uart;
    USART_SERIAL_SETUP config_usart;
    sercom_registers_t *regs;

    RT_ASSERT (serial != RT_NULL);
    RT_ASSERT (cfg != RT_NULL);

    config_usart.baudRate = cfg->baud_rate;
    uart = (SAMD2x_UART_T *) (serial->parent.user_data);
    regs = uart->sercom_reg;

    switch (cfg->data_bits ) {
        case DATA_BITS_8:
            config_usart.dataWidth = USART_DATA_8_BIT;
            break;

        case DATA_BITS_5:
            config_usart.dataWidth = USART_DATA_5_BIT;
            break;

        case DATA_BITS_6:
            config_usart.dataWidth = USART_DATA_6_BIT;
            break;

        case DATA_BITS_7:
            config_usart.dataWidth = USART_DATA_7_BIT;
            break;

        case DATA_BITS_9:
            config_usart.dataWidth = USART_DATA_9_BIT;
            break;

        default:
            config_usart.dataWidth = USART_DATA_8_BIT;
            break;
    }

    switch (cfg->parity) {
        case PARITY_NONE:
            config_usart.parity = USART_PARITY_NONE;
            break;

        case PARITY_EVEN:
            config_usart.parity = USART_PARITY_EVEN;
            break;

        case PARITY_ODD:
            config_usart.parity = USART_PARITY_ODD;
            break;

        default:
            config_usart.parity = USART_PARITY_NONE;
            break;
    }

    config_usart.stopBits = USART_STOP_1_BIT;
    if (cfg->stop_bits != USART_STOP_1_BIT)
        config_usart.stopBits = USART_STOP_2_BIT;

    while (SERCOM_USART_SerialSetup (regs, &config_usart, SERCOM_USART_FrequencyGet (regs)) != true);

    return RT_EOK;
}

static rt_err_t _uart_ctrl (struct rt_serial_device *serial, int cmd, void *arg)
{
    SAMD2x_UART_T *uart;

    RT_ASSERT (serial != RT_NULL);

    uart = (SAMD2x_UART_T *) (serial->parent.user_data);

    switch (cmd) {
        /* disable interrupt */
        case RT_DEVICE_CTRL_CLR_INT:
            /* Disable RX interrupt. */
            /* Disable the RX Complete Interrupt */
            uart->sercom_reg->USART_INT.SERCOM_INTENCLR = SERCOM_USART_INT_INTENCLR_RXC (1);
            uart->sercom_reg->USART_INT.SERCOM_CTRLB = SERCOM_USART_INT_CTRLB_RXEN (0);
            NVIC_DisableIRQ (uart->vector);
            break;
        /* enable interrupt */
        case RT_DEVICE_CTRL_SET_INT:
            /* Enable RX interrupt. */
            /* Enable the RX Complete Interrupt */
            uart->sercom_reg->USART_INT.SERCOM_INTENSET = SERCOM_USART_INT_INTENSET_RXC (1);
            uart->sercom_reg->USART_INT.SERCOM_CTRLB = SERCOM_USART_INT_CTRLB_RXEN (1);
            NVIC_EnableIRQ (uart->vector);
            break;

        default:
            return RT_ERROR;
    }

    return RT_EOK;
}

static int _uart_putc (struct rt_serial_device *serial, char c)
{
    SAMD2x_UART_T *uart;
    sercom_registers_t *regs;

#if 1
    RT_ASSERT (serial != RT_NULL);
    uart = (SAMD2x_UART_T *) (serial->parent.user_data);
    regs = uart->sercom_reg;

    while ((regs->USART_INT.SERCOM_INTFLAG & SERCOM_USART_INT_INTFLAG_DRE_Msk) !=
           SERCOM_USART_INT_INTFLAG_DRE_Msk);

    regs->USART_INT.SERCOM_DATA = c;

    return 1;
#else
    RT_ASSERT (serial != RT_NULL);
    uart = (SAMD2x_UART_T *) (serial->parent.user_data);
    regs = uart->sercom_reg;

    // Initiate the transfer by sending first byte
    if ((regs->USART_INT.SERCOM_INTFLAG & SERCOM_USART_INT_INTFLAG_DRE_Msk) ==
        SERCOM_USART_INT_INTFLAG_DRE_Msk) {
        regs->USART_INT.SERCOM_DATA = c;

        while (! (regs->USART_INT.SERCOM_INTFLAG & SERCOM_USART_INT_INTFLAG_TXC_Msk)) {
            // Wait until data is sent
        }
        return 1;
    }

    return -1;
#endif
}

static int _uart_getc (struct rt_serial_device *serial)
{
    int ch;
    SAMD2x_UART_T *uart;
    sercom_registers_t *regs;

    RT_ASSERT (serial != RT_NULL);
    uart = (SAMD2x_UART_T *) (serial->parent.user_data);
    regs = uart->sercom_reg;

    /* Check if USART has new data */
    if (! (regs->USART_INT.SERCOM_INTFLAG & SERCOM_USART_INT_INTFLAG_RXC_Msk)) {
        /* Return error code */
        return -1;
    }

    ch = regs->USART_INT.SERCOM_DATA & 0x1FF;

    return ch;
}

static struct rt_uart_ops _uart_ops = {
    _uart_cfg,
    _uart_ctrl,
    _uart_putc,
    _uart_getc
};

static void uart_int_cb (SAMD2x_UART_T *uart_handle)
{
    sercom_registers_t *regs;

    RT_ASSERT (uart_handle != RT_NULL);
    regs = uart_handle->sercom_reg;

    if (regs->USART_INT.SERCOM_INTENSET != 0) {
        /* Checks for receive complete empty flag */
        if ((regs->USART_INT.SERCOM_INTFLAG & SERCOM_USART_INT_INTFLAG_RXC_Msk) == SERCOM_USART_INT_INTFLAG_RXC_Msk) {
            //SERCOM4_USART_ISR_RX_Handler();
            rt_hw_serial_isr (uart_handle->serial, RT_SERIAL_EVENT_RX_IND);
        }

        /* Checks for error flag */
        if ((regs->USART_INT.SERCOM_INTFLAG & SERCOM_USART_INT_INTFLAG_ERROR_Msk) == SERCOM_USART_INT_INTFLAG_ERROR_Msk) {
            SERCOM_USART_ISR_ERR_Handler (regs);
        }
    }
}

#if (USE_SERCOM4_USART > 0)
void SERCOM4_USART_InterruptHandler ( void )
{
    /* enter interrupt */
    rt_interrupt_enter();

    uart_int_cb (&_uart4);

    /* leave interrupt */
    rt_interrupt_leave();
}
#endif

static int samd21_rtt_uart_init (void)
{
    int result = RT_EOK;
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;

    SERCOM_USART_ClockInit();
    SERCOM_USART_PortInit();
    SERCOM_USART_NvicInit();

    config.bufsz = 128;
#if (USE_SERCOM4_USART > 0)
    SERCOM_USART_Initialize (_uart4.sercom_reg);

    _serial4.config = config;
    _serial4.ops = &_uart_ops;

    if (rt_hw_serial_register (&_serial4, "uart4", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, &_uart4) == RT_EOK) {
        rt_kprintf ("%s register success\n", "uart4");
    } else {
        rt_kprintf ("%s register failed\n", "uart4");
        result = -RT_ERROR;
    }
#endif

    return result;
}
INIT_DEVICE_EXPORT (samd21_rtt_uart_init);

#endif